/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package com.googlecode.datawander.codegenerator;

import com.googlecode.datawander.i18n.I18n;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.List;
import org.apache.log4j.Logger;

/**
 * Class to generate source code of DB4oUtil
 * @author MarcinStachniuk
 */
public class DatabasesConnectorsGenerator {

    private static final Logger logger = Logger.getLogger(DatabasesConnectorsGenerator.class);

    /**
     * New line char. Use to generate new line in file.
     */
    public static final String NEW_LINE;

    /**
     * String with one ident in files with source code
     */
    private static final String INDENT = "    ";

    private List<String> metaClassList;

    // static inicialize block
    static {
        NEW_LINE = System.getProperty("line.separator");
    }

    private String packageName = null;

    /**
     * Generate Db4oUtil.java source code.
     * @param metaClassList List with names of classes that are set
     * cascadeOnUpdate on true.
     * @param filePath Path where file should be created.
     * @throws CodeGenerationException If it was a problem with generation of
     * source code.
     */
    public void generateDb4oUtilSource(List<String> metaClassList, String filePath) throws CodeGenerationException {
        logger.trace("generateDb4oUtilSource(...m " + filePath + ")");

        this.metaClassList = metaClassList;

        String fileAbsolutePath = null;
        if(packageName != null && packageName.length() != 0) {
            String dirPath = filePath + File.separator +
                    packageName.replace(".", File.separator);
            File dirFile = new File(dirPath);
            if(!dirFile.exists()) {
                dirFile.mkdirs();
            }
            fileAbsolutePath = dirPath
                    + File.separatorChar + "Db4oUtil.java";
        } else {
             fileAbsolutePath = filePath + File.separatorChar + "Db4oUtil.java";
        }
        String message = I18n.getString("db4outilpath");
        message = String.format(message, fileAbsolutePath);
        logger.debug(message);

        File file = new File(fileAbsolutePath);

        try {
            file.createNewFile();
        } catch (IOException ex) {
            logger.error(ex, ex);
            throw new CodeGenerationException(ex.getMessage());
        }
        try {
            PrintWriter out = new PrintWriter(file);

            // generation head od file
            out.append(SourceCodeGenerator2Object.SOURCE_FILE_HEADER);

            generatePackage(out);

            generateImports(out);

            generateClass(out);

            generateFielsd(out);

            genarateGetObjectContainerMethod(out);

            generateCloseObjectContainerMethod(out);

            genearateShutdownMethod(out);

            generateGetDatabaseFilename(out);

            generateSetDatabaseFilename(out);

            generateDefragDatabaseMethod(out);

            out.append("}" + NEW_LINE);

            out.close();

        } catch (FileNotFoundException ex) {
            logger.error(ex, ex);
            throw new CodeGenerationException(
                    I18n.getString("codegenerationexceptionproblem"));
        }
    }

    private void generateImports(PrintWriter out) {
        out.append("import com.db4o.Db4o;" + NEW_LINE);
        out.append("import com.db4o.ObjectContainer;" + NEW_LINE);
        out.append("import com.db4o.ObjectServer;" + NEW_LINE);
        out.append("import com.db4o.config.Configuration;" + NEW_LINE);
        out.append("import com.db4o.defragment.Defragment;" + NEW_LINE);
        out.append("import com.db4o.defragment.DefragmentConfig;" + NEW_LINE);
        out.append("import com.db4o.defragment.StoredClassFilter;" + NEW_LINE);
        out.append("import com.db4o.ext.StoredClass;" + NEW_LINE);
        out.append("import java.io.IOException;" + NEW_LINE);
        out.append("import java.math.BigDecimal;" + NEW_LINE);
    }

    private void generateClass(PrintWriter out) {
        out.append("/**" + NEW_LINE);
        out.append(" * Version for db4o 7.4" + NEW_LINE);
        out.append(" * @author MarcinStachniuk" + NEW_LINE);
        out.append(" */" + NEW_LINE);
        out.append("public class Db4oUtil {" + NEW_LINE);
    }

    private void generateFielsd(PrintWriter out) {
        out.append(INDENT + "private static String databaseFilename = \"db4o.yap\";" + NEW_LINE);
        out.append(INDENT + "private static ObjectServer objectServer;" + NEW_LINE);
        out.append(INDENT + "private static final ThreadLocal<ObjectContainer> dbThreadLocal = new ThreadLocal<ObjectContainer>();" + NEW_LINE + NEW_LINE);
    }

    private void genarateGetObjectContainerMethod(PrintWriter out) {
        out.append(INDENT + "public static ObjectContainer getObjectContainer() {" + NEW_LINE);
        out.append(INDENT + INDENT + "ObjectContainer oc = (ObjectContainer) dbThreadLocal.get();" + NEW_LINE);
        out.append(INDENT + INDENT + "if (oc == null || oc.ext().isClosed()) {" + NEW_LINE);

        generateConfigurationForObjects(out);

        out.append(INDENT + INDENT + INDENT + "conf.activationDepth(Integer.MAX_VALUE);" + NEW_LINE);

        out.append(INDENT + INDENT + INDENT + "// save BigDecimal in db4o 7.4" + NEW_LINE);
        out.append(INDENT + INDENT + INDENT + 
                "conf.objectClass(BigDecimal.class).callConstructor(true);" + NEW_LINE);
        out.append(INDENT + INDENT + INDENT +
                "conf.objectClass(BigDecimal.class).storeTransientFields(true);" + NEW_LINE);


        out.append(INDENT + INDENT + INDENT + 
                "oc = Db4o.openFile(conf, databaseFilename);" + NEW_LINE);
        out.append(INDENT + INDENT + INDENT + 
                "dbThreadLocal.set(oc);" + NEW_LINE);

        out.append(INDENT + INDENT + "}" + NEW_LINE);
        out.append(INDENT + INDENT + "return oc;" + NEW_LINE);
        out.append(INDENT + "}" + NEW_LINE + NEW_LINE);
    }

    private void generateConfigurationForObjects(PrintWriter out) {
        out.append(INDENT + INDENT + INDENT + "Configuration conf = Db4o.newConfiguration();" + NEW_LINE);
        for(String mc : metaClassList) {
            out.append(INDENT + INDENT + INDENT + "conf.objectClass(" +
                    mc + ".class).cascadeOnUpdate(true);"+
                    NEW_LINE);
        }
        out.append(NEW_LINE);
    }

    private void generateCloseObjectContainerMethod(PrintWriter out) {
        out.append(INDENT + "public static void closeObjectContainer() {" + NEW_LINE);
        out.append(INDENT + INDENT + "ObjectContainer oc = (ObjectContainer) dbThreadLocal.get();" + NEW_LINE);
        out.append(INDENT + INDENT + "dbThreadLocal.set(null);" + NEW_LINE);
        out.append(INDENT + INDENT + "if (oc != null) oc.close();" + NEW_LINE);
        out.append(INDENT + "}" + NEW_LINE + NEW_LINE);

    }

    private void genearateShutdownMethod(PrintWriter out) {
        out.append(INDENT + "public static void shutdown() {" + NEW_LINE);
        out.append(INDENT + INDENT + "if (objectServer != null) {" + NEW_LINE);
        out.append(INDENT + INDENT + INDENT + "objectServer.close();" + NEW_LINE);
        out.append(INDENT + INDENT + "}" + NEW_LINE);
        out.append(INDENT + "}" + NEW_LINE + NEW_LINE);
    }

    private void generateGetDatabaseFilename(PrintWriter out) {
        out.append(INDENT + "public static String getDatabaseFilename() {" + NEW_LINE);
        out.append(INDENT + INDENT + "return databaseFilename;" + NEW_LINE);
        out.append(INDENT + "}" + NEW_LINE + NEW_LINE);
    }

    private void generateSetDatabaseFilename(PrintWriter out) {
        out.append(INDENT + "public static void setDatabaseFilename(String databaseFilename) {" +
                NEW_LINE);
        out.append(INDENT + INDENT + "Db4oUtil.databaseFilename = databaseFilename;" +
                NEW_LINE);
        out.append(INDENT + "}" + NEW_LINE + NEW_LINE);
    }

    private void generateDefragDatabaseMethod(PrintWriter out) {
        out.append(INDENT + "public static void defragDatabase() throws IOException {" + NEW_LINE);
        out.append(INDENT + INDENT + "DefragmentConfig config = new DefragmentConfig" +
                "(Db4oUtil.getDatabaseFilename());" + NEW_LINE);
        out.append(INDENT + INDENT + "config.forceBackupDelete(true);" + NEW_LINE);
        out.append(INDENT + INDENT + "config.storedClassFilter(new StoredClassFilter() {" + NEW_LINE);
        out.append(INDENT + INDENT + INDENT + "public boolean accept(StoredClass sc) {" + NEW_LINE);
        out.append(INDENT + INDENT + INDENT + INDENT + "if(sc.instanceCount() == 0) {" + NEW_LINE);
        out.append(INDENT + INDENT + INDENT + INDENT + INDENT + "return false;" + NEW_LINE);
        out.append(INDENT + INDENT + INDENT + INDENT + "}" + NEW_LINE);
        out.append(INDENT + INDENT + INDENT + INDENT + "return true;" + NEW_LINE);
        out.append(INDENT + INDENT + INDENT + "}" + NEW_LINE);
        out.append(INDENT + INDENT + "});" + NEW_LINE);
        out.append(INDENT + INDENT + "Defragment.defrag(config);" + NEW_LINE);
        out.append(INDENT + "}" + NEW_LINE);
    }

    

    public String getPackageName() {
        return packageName;
    }

    public void setPackageName(String packageName) {
        this.packageName = packageName;
    }

    private void generatePackage(PrintWriter out) {
        if(packageName != null) {
            out.append("package " + packageName + ";" + NEW_LINE + NEW_LINE);
        }
    }

    public String getClassName() {
        if(packageName != null) {
            return (packageName.endsWith(".") ? packageName : packageName + ".")
                    + "Db4oUtil";
        } else {
            return "Db4oUtil";
        }
    }


}
